package com.xrh.xdemo.service.aspects;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import com.xrh.core.bean.CommonResult;
import com.xrh.xdemo.api.dto.extend.OperateTraceDTO;
import com.xrh.xdemo.api.dto.extend.SysUserDetailDTO;
import com.xrh.xdemo.service.annotations.OperateTrace;
import com.xrh.xdemo.service.service.OperateTraceService;
import com.xrh.xdemo.service.utils.LoginUtils;
import com.xrh.xdemo.service.utils.ServletUtils;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;


/**
 * @function 拦截注解RepeatRequest声明的方法
 * @author 肖荣辉
 * @date 2023年12月21日
*/
@Aspect
@Slf4j
@Component
public class OperateTraceAspect {
	
	@Pointcut("@annotation(com.xrh.xdemo.service.annotations.OperateTrace)")
	public void operateTraceCut() {};

    //当前线程缓存
    private  static ThreadLocal<String> keyThreadLocal = new ThreadLocal<String>();
    
    /**
	 * @function 设置固定标题(当前线程)
	 * @param title
	 * @author 肖荣辉
	 * @date 2024年1月5日
	*/
    public static  void setTitle(String title) {
    	
    	//不为空设置当前线程的title
    	if(StrUtil.isNotBlank(title))
    		keyThreadLocal.set(title);
    	
    }
    
    /**
	 * @function 追加固定标题(当前线程)
	 * @param title
	 * @author 肖荣辉
	 * @date 2024年1月5日
	*/
    public static  void appendTitle(String title) {
    	
    	if(StrUtil.isBlank(title))
    		return;
    	
    	String newTitle = getTitle();
    	
    	newTitle = StrUtil.isBlank(newTitle) ? title : newTitle + "," + title;
    	
    	setTitle(newTitle);
    	
    }
    
    /**
	 * @function 获取当前线程设置的标题
	 * @author 肖荣辉
	 * @date 2024年1月5日
	*/
    public static String getTitle() {
    	return keyThreadLocal.get();
    }
    
    /**
	 * @function 抛出异常的时候记录
	 * @author 肖荣辉
	 * @date 2024年1月4日
	*/
    @AfterThrowing(pointcut = "operateTraceCut()")
    public void afterThrowing(JoinPoint joinPoint) {
    	
    	//操作失败不记录
    	
    }
    
    /**
	 * @function 方法返回之后
	 * @author 肖荣辉
	 * @date 2024年1月4日
	*/
    @AfterReturning(pointcut = "operateTraceCut()" , returning = "rst")
    public void afterReturning(JoinPoint joinPoint ,  Object rst) {
    	
    	//只记录返回成功的日志
    	Method method = getMethod(joinPoint);
    	
    	OperateTrace operateTrace = method.getAnnotation(OperateTrace.class);
    	
    	JSONObject paramsMap = getParamsMap(joinPoint);
    	
    	//打印操作痕迹
    	saveOperateTrace(paramsMap, operateTrace , rst);
    }
    
    /**
	 * @function 保存操作痕迹
	 * @author 肖荣辉
	 * @date 2024年1月4日
	*/
    public void saveOperateTrace(JSONObject paramsMap , OperateTrace operateTrace , Object rst) {
    	
    	SysUserDetailDTO user = LoginUtils.getUser();
    	
    	if(user == null)
    		return;
    	
    	//若是返回结果是CommonResult , 若不成功则不记录
    	if(rst != null && rst instanceof CommonResult && !((CommonResult<?>)rst).requestIsSuccess()) {
    		return;
    	}
    	
    	//结果字符
    	String rstValue = rst == null ? null : rst.toString();
    	
    	//结果json, 如果是普通类型 , 直接会转为一个{};
    	String rstJson = JSONUtil.toJsonStr(rst);
    	
    	//结果json对象
    	JSONObject rstObj =  JSONUtil.isJson(rstJson) ?  JSONUtil.parseObj(rstJson) : new JSONObject();
    
    	//操作轨迹信息
    	OperateTraceDTO traceInfo = new OperateTraceDTO();
    	
    	//若设置了业务类型则取业务类型
    	if(StrUtil.isNotBlank(operateTrace.businessType())) {
    		traceInfo.setBusinessType(operateTrace.businessType());
    	}

    	
    	//若业务字段取自返回值里头
    	if(operateTrace.businessNoFromReturnResult()) {
    		traceInfo.setBusinessNo(MapUtil.isEmpty(rstObj) ? rstValue : getStrFromJSONObject(rstObj, operateTrace.businessNoKeyPath()));
    	}
    	//否则取自参数里头的
    	else if(StrUtil.isNotBlank(operateTrace.businessNoKeyPath())) {
    		traceInfo.setBusinessNo(getStrFromJSONObject(paramsMap, operateTrace.businessNoKeyPath()));
    	}
    	
    	//若设置了固定标题, 则优先取固定标题
    	if(StrUtil.isNotBlank(operateTrace.title())) {
    		traceInfo.setTitle(operateTrace.title());
    	}
    	//若标题取自返回结果里头
    	else if(operateTrace.titleFromReturnResult()) {
    		traceInfo.setTitle(MapUtil.isEmpty(rstObj) ? rstValue : getStrFromJSONObject(rstObj, operateTrace.titleKeyPath()));
    	}
    	//否则取自参数里头
    	else if(StrUtil.isNotBlank(operateTrace.titleKeyPath())){
    		traceInfo.setTitle(getStrFromJSONObject(paramsMap, operateTrace.titleKeyPath()));
    	}
    	
    	//若前面都没有获取到标题 , 则从本地线程中获取
    	if(StrUtil.isBlank(traceInfo.getTitle()) && StrUtil.isNotBlank(getTitle())) {
    		traceInfo.setTitle(getTitle());
    	}
    	
    	//清除本线程内容
    	keyThreadLocal.remove();
    	
    	//操作用户信息
    	traceInfo.setOperateUserId(user.getId());
    	traceInfo.setOperateUserName(user.getUserName());
    	traceInfo.setOperateUserAccount(user.getAccount());
    	traceInfo.setOperateUserJobNo(null);
    	traceInfo.setOperateTime(new Date());
    	traceInfo.setOperateType(operateTrace.operateType() );
    	
    	//打印轨迹
    	printTraceInfo(traceInfo);
    	
    	//异步新增操作痕迹
    	SpringUtil.getBean(OperateTraceService.class).addOperateTrace(traceInfo);
    	
    }
    
    /**
	 * @function 打印操作轨迹
	 * @param traceInfo
	 * @author 肖荣辉
	 * @date 2024年1月5日
	*/
    public void printTraceInfo(OperateTraceDTO traceInfo) {
    	
    	String txt = traceInfo.getOperateUserName() + "【"+traceInfo.getOperateType()+"】";
    	
    	if(StrUtil.isNotBlank(traceInfo.getBusinessType()))
    		txt += " " + traceInfo.getBusinessType();
    	
    	if(StrUtil.isNotBlank(traceInfo.getBusinessNo()))
    		txt += " 【" + traceInfo.getBusinessNo() + "】";
    	
    	if(StrUtil.isNotBlank(traceInfo.getTitle()))
    		txt += " " + traceInfo.getTitle();
    	
    	log.info(txt);
    }
    
    /**
	 * @function 获取方法
	 * @author 肖荣辉
	 * @date 2023年12月21日
	*/
    public Method getMethod(JoinPoint point) {
    	
        // 处理，声明在类上的情况
        MethodSignature signature = (MethodSignature) point.getSignature();
        
        Method method = signature.getMethod();
        
        if (!method.getDeclaringClass().isInterface()) {
            return method;
        }

        // 处理，声明在接口上的情况
        try {
            return point.getTarget().getClass().getDeclaredMethod( point.getSignature().getName(), method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
        
    }
    
    /**
	 * @function 获取参数
	 * @param joinPoint
	 * @author 肖荣辉
	 * @date 2024年1月4日
	*/
    public JSONObject getParamsMap(JoinPoint joinPoint){
    	
    	Object[] args = 	joinPoint.getArgs();
    	
    	Map<String , String> paramsMap = ServletUtils.getRequest() == null ? new HashMap<String, String>() : ServletUtil.getParamMap(ServletUtils.getRequest());
		
    	JSONObject paramsObj = JSONUtil.parseObj(paramsMap);
    	
    	//无论是表单的参数还是json中的都放到一起
    	for(int i = 0 ; args != null && i < 	args.length ; i ++) {
    		
    		Object arg = args[i];
    		
    		String argValue = arg != null ? arg.toString() : null;
    		
    		if(argValue == null) continue;
    		
    		String jsonValue = JSONUtil.toJsonStr(arg);
    		
    		JSONObject obj = JSONUtil.isJson(jsonValue) ? JSONUtil.parseObj(jsonValue)  : null;
    		
    		if(MapUtil.isNotEmpty(obj))
    			paramsObj.putAll(obj);
    		
    	}
    	
    	return paramsObj;
    }
    
	/**
	 * @function  获取某个路径上key的JSONObject值
	 * @param keyPath key路径 ， 举例：msg.body.row , 表示从data中获取msg下body下的row的值
	 * @author 肖荣辉
	 * @date 2022年12月9日
	 */
	public static JSONObject getJSONObjectFromJSONObject(JSONObject data ,  String keyPath) {
		return getValueFromJSONObject(data, JSONObject.class, keyPath);
	}
	
	/**
	 * @function  获取某个路径上key的String值
	 * @param keyPath key路径 ， 举例：msg.body.row , 表示从data中获取msg下body下的row的值
	 * @author 肖荣辉
	 * @date 2022年12月9日
	 */
	public static String getStrFromJSONObject(JSONObject data ,  String keyPath) {
		return getValueFromJSONObject(data, String.class, keyPath);
	}
	
	/**
	 * @function  获取某个路径上key的int值
	 * @param keyPath key路径 ， 举例：msg.body.row , 表示从data中获取msg下body下的row的值
	 * @author 肖荣辉
	 * @date 2022年12月9日
	 */
	public static Integer getIntFromJSONObject(JSONObject data ,  String keyPath) {
		return getValueFromJSONObject(data, Integer.class, keyPath);
	}
	
	/**
	 * @function  获取某个路径上key的值
	 * @param keyPath key路径 ， 举例：msg.body.row , 表示从data中获取msg下body下的row的值
	 * @author 肖荣辉
	 * @date 2022年12月9日
	 */
	public static <T> T getValueFromJSONObject(JSONObject data , Class<T> clazz , String keyPath) {
		
		if(MapUtil.isEmpty(data))
			return null;
		
		if(StrUtil.isBlank(keyPath))
			return null;
		
		String[] keys = keyPath.split("\\.");
		
		for(int i = 0 ; i< keys.length ; i ++) {
			
			String key = keys[i];
			
			if(MapUtil.isEmpty(data))
				return null;
			
			if(!data.containsKey(key))
				return null;
			
			//若不是最后一个
			if((i + 1) != keys.length) {
				data = data.getJSONObject(key);
				continue;
			}
			
			return data.get(key, clazz);
		}
		
		return null;
	}
	
}
